Skip to main content

libobs_wrapper\data\output/
replay_buffer.rs

1//! Provides functionality for working with OBS replay buffers.
2//!
3//! This module extends the ObsOutputRef to provide replay buffer capabilities.
4//! A replay buffer is a special type of output that continuously records
5//! the last N seconds of content, allowing the user to save this buffer on demand. This must be configured. More documentation soon.
6use std::{
7    path::{Path, PathBuf},
8    sync::Arc,
9};
10
11use crate::{
12    data::{
13        object::ObsObjectTrait,
14        output::{ObsOutputRef, ObsOutputTraitSealed},
15    },
16    forward_obs_object_impl, forward_obs_output_impl, impl_signal_manager, run_with_obs,
17    runtime::ObsRuntime,
18    unsafe_send::{Sendable, SmartPointerSendable},
19    utils::{ObsCalldataExt, ObsError, ObsString, OutputInfo},
20};
21
22#[derive(Debug, Clone)]
23/// A reference to an OBS output.
24///
25/// This struct is used specifically for the replay buffer to manage saving the buffer to a file
26/// and configuring special settings, which are specific to the replay buffer
27///
28/// The output is associated with video and audio encoders that convert
29/// raw media to the required format before sending/storing.
30pub struct ObsReplayBufferOutputRef {
31    /// Disconnect signals first
32    replay_signal_manager: Arc<ObsReplayOutputSignals>,
33
34    output: ObsOutputRef,
35}
36
37impl ObsOutputTraitSealed for ObsReplayBufferOutputRef {
38    fn new(mut output: OutputInfo, runtime: ObsRuntime) -> Result<Self, ObsError> {
39        output.id = ObsString::new("replay_buffer");
40        let output = ObsOutputRef::new(output, runtime.clone())?;
41
42        let replay_signal_manager =
43            ObsReplayOutputSignals::new(&output.__native_handle(), runtime)?;
44        Ok(Self {
45            replay_signal_manager: Arc::new(replay_signal_manager),
46            output,
47        })
48    }
49
50    fn video_encoder_slot(
51        &self,
52    ) -> &std::sync::Arc<
53        std::sync::RwLock<Option<std::sync::Arc<crate::encoders::video::ObsVideoEncoder>>>,
54    > {
55        self.output.video_encoder_slot()
56    }
57
58    fn audio_encoder_slots(
59        &self,
60    ) -> &std::sync::Arc<
61        std::sync::RwLock<
62            std::collections::HashMap<
63                usize,
64                std::sync::Arc<crate::encoders::audio::ObsAudioEncoder>,
65            >,
66        >,
67    > {
68        self.output.audio_encoder_slots()
69    }
70
71    fn service_slot(
72        &self,
73    ) -> &std::sync::Arc<std::sync::RwLock<Option<std::sync::Arc<crate::services::ObsServiceRef>>>>
74    {
75        self.output.service_slot()
76    }
77
78    fn configuration_lock(&self) -> &std::sync::Arc<std::sync::Mutex<()>> {
79        self.output.configuration_lock()
80    }
81}
82
83forward_obs_object_impl!(ObsReplayBufferOutputRef, output, *mut libobs::obs_output);
84forward_obs_output_impl!(ObsReplayBufferOutputRef, output);
85
86impl_signal_manager!(|ptr: SmartPointerSendable<*mut libobs::obs_output>| {
87    unsafe {
88        // Safety: Again, it carries a reference of the drop guard so we must have a valid pointer
89        libobs::obs_output_get_signal_handler(ptr.get_ptr())
90    }
91}, ObsReplayOutputSignals for *mut libobs::obs_output, [
92    "saved": {}
93]);
94
95impl ObsReplayBufferOutputRef {
96    pub fn replay_signals(&self) -> &Arc<ObsReplayOutputSignals> {
97        &self.replay_signal_manager
98    }
99    /// Saves the current replay buffer content to disk.
100    ///
101    /// # Implementation Details
102    /// This method:
103    /// 1. Accesses the OBS procedure handler for the output
104    /// 2. Calls the "save" procedure to trigger saving the replay
105    /// 3. Calls the "get_last_replay" procedure to retrieve the saved file path
106    /// 4. Extracts the path string from the calldata and returns it
107    ///
108    /// # Returns
109    /// * `Ok(Box<Path>)` - The path to the saved replay file
110    /// * `Err(ObsError)` - Various errors that might occur during the saving process:
111    ///   - Failure to get procedure handler
112    ///   - Failure to call "save" procedure
113    ///   - Failure to call "get_last_replay" procedure
114    ///   - Failure to extract the path from calldata
115    pub fn save_buffer(&self) -> Result<Box<Path>, ObsError> {
116        if self.runtime().is_actor_thread() {
117            return Err(ObsError::RuntimeReentrantBlocking);
118        }
119        log::trace!("Saving replay buffer...");
120        let output_ptr = self.__native_handle();
121
122        log::trace!("Getting procedure handler for replay buffer output...");
123        let proc_handler = run_with_obs!(self.runtime().clone(), (output_ptr), move || {
124            // Safety: At this point, output_ptr MUST be a valid pointer as we haven't released the output yet.
125            let ph = unsafe { libobs::obs_output_get_proc_handler(output_ptr.get_ptr()) };
126            if ph.is_null() {
127                return Err(ObsError::OutputSaveBufferFailure(
128                    "Failed to get proc handler.".to_string(),
129                ));
130            }
131            Ok(Sendable(ph))
132        })??;
133
134        log::trace!("Calling 'save' procedure on replay buffer output...");
135        // Safety: we know that the proc handler is valid because we got it from OBS earlier
136        unsafe { self.runtime().call_proc_handler(&proc_handler, "save")? };
137
138        log::trace!("Waiting for 'saved' signal from replay buffer output...");
139        self.replay_signals()
140            .on_saved()?
141            .blocking_recv()
142            .map_err(|_e| {
143                ObsError::OutputSaveBufferFailure(
144                    "Failed to receive saved replay buffer path.".to_string(),
145                )
146            })?;
147
148        log::trace!("Retrieving last replay path from replay buffer output...");
149        // Safety: We know that the proc handler is valid because we got it from OBS earlier
150        let mut calldata = unsafe {
151            self.runtime()
152                .call_proc_handler(&proc_handler, "get_last_replay")?
153        };
154
155        log::trace!("Extracting path from calldata...");
156        let path = calldata.get_string("path")?;
157        let path = PathBuf::from(path);
158
159        Ok(path.into_boxed_path())
160    }
161}